home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 68161 / 68161.xpi / chrome / content / lib / showdown.js
Text File  |  2009-04-19  |  34KB  |  1,298 lines

  1. //
  2. // showdown.js -- A javascript port of Markdown.
  3. //
  4. // Copyright (c) 2007 John Fraser.
  5. //
  6. // Original Markdown Copyright (c) 2004-2005 John Gruber
  7. //   <http://daringfireball.net/projects/markdown/>
  8. //
  9. // Redistributable under a BSD-style open source license.
  10. // See license.txt for more information.
  11. //
  12. // The full source distribution is at:
  13. //
  14. //                A A L
  15. //                T C A
  16. //                T K B
  17. //
  18. //   <http://www.attacklab.net/>
  19. //
  20.  
  21. //
  22. // Wherever possible, Showdown is a straight, line-by-line port
  23. // of the Perl version of Markdown.
  24. //
  25. // This is not a normal parser design; it's basically just a
  26. // series of string substitutions.  It's hard to read and
  27. // maintain this way,  but keeping Showdown close to the original
  28. // design makes it easier to port new features.
  29. //
  30. // More importantly, Showdown behaves like markdown.pl in most
  31. // edge cases.  So web applications can do client-side preview
  32. // in Javascript, and then build identical HTML on the server.
  33. //
  34. // This port needs the new RegExp functionality of ECMA 262,
  35. // 3rd Edition (i.e. Javascript 1.5).  Most modern web browsers
  36. // should do fine.  Even with the new regular expression features,
  37. // We do a lot of work to emulate Perl's regex functionality.
  38. // The tricky changes in this file mostly have the "attacklab:"
  39. // label.  Major or self-explanatory changes don't.
  40. //
  41. // Smart diff tools like Araxis Merge will be able to match up
  42. // this file with markdown.pl in a useful way.  A little tweaking
  43. // helps: in a copy of markdown.pl, replace "#" with "//" and
  44. // replace "$text" with "text".  Be sure to ignore whitespace
  45. // and line endings.
  46. //
  47.  
  48.  
  49. //
  50. // Showdown usage:
  51. //
  52. //   var text = "Markdown *rocks*.";
  53. //
  54. //   var converter = new Showdown.converter();
  55. //   var html = converter.makeHtml(text);
  56. //
  57. //   alert(html);
  58. //
  59. // Note: move the sample code to the bottom of this
  60. // file before uncommenting it.
  61. //
  62.  
  63.  
  64. //
  65. // Showdown namespace
  66. //
  67. var Showdown = {};
  68.  
  69. //
  70. // converter
  71. //
  72. // Wraps all "globals" so that the only thing
  73. // exposed is makeHtml().
  74. //
  75. Showdown.converter = function() {
  76.  
  77. //
  78. // Globals:
  79. //
  80.  
  81. // Global hashes, used by various utility routines
  82. var g_urls;
  83. var g_titles;
  84. var g_html_blocks;
  85.  
  86. // Used to track when we're inside an ordered or unordered list
  87. // (see _ProcessListItems() for details):
  88. var g_list_level = 0;
  89.  
  90.  
  91. this.makeHtml = function(text) {
  92. //
  93. // Main function. The order in which other subs are called here is
  94. // essential. Link and image substitutions need to happen before
  95. // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
  96. // and <img> tags get encoded.
  97. //
  98.  
  99.     // Clear the global hashes. If we don't clear these, you get conflicts
  100.     // from other articles when generating a page which contains more than
  101.     // one article (e.g. an index page that shows the N most recent
  102.     // articles):
  103.     g_urls = new Array();
  104.     g_titles = new Array();
  105.     g_html_blocks = new Array();
  106.  
  107.     // attacklab: Replace ~ with ~T
  108.     // This lets us use tilde as an escape char to avoid md5 hashes
  109.     // The choice of character is arbitray; anything that isn't
  110.     // magic in Markdown will work.
  111.     text = text.replace(/~/g,"~T");
  112.  
  113.     // attacklab: Replace $ with ~D
  114.     // RegExp interprets $ as a special character
  115.     // when it's in a replacement string
  116.     text = text.replace(/\$/g,"~D");
  117.  
  118.     // Standardize line endings
  119.     text = text.replace(/\r\n/g,"\n"); // DOS to Unix
  120.     text = text.replace(/\r/g,"\n"); // Mac to Unix
  121.  
  122.     // Make sure text begins and ends with a couple of newlines:
  123.     text = "\n\n" + text + "\n\n";
  124.  
  125.     // Convert all tabs to spaces.
  126.     text = _Detab(text);
  127.  
  128.     // Strip any lines consisting only of spaces and tabs.
  129.     // This makes subsequent regexen easier to write, because we can
  130.     // match consecutive blank lines with /\n+/ instead of something
  131.     // contorted like /[ \t]*\n+/ .
  132.     text = text.replace(/^[ \t]+$/mg,"");
  133.  
  134.     // Turn block-level HTML blocks into hash entries
  135.     text = _HashHTMLBlocks(text);
  136.  
  137.     // Strip link definitions, store in hashes.
  138.     text = _StripLinkDefinitions(text);
  139.  
  140.     text = _RunBlockGamut(text);
  141.  
  142.     text = _UnescapeSpecialChars(text);
  143.  
  144.     // attacklab: Restore dollar signs
  145.     text = text.replace(/~D/g,"$$");
  146.  
  147.     // attacklab: Restore tildes
  148.     text = text.replace(/~T/g,"~");
  149.  
  150.     return text;
  151. }
  152.  
  153.  
  154. var _StripLinkDefinitions = function(text) {
  155. //
  156. // Strips link definitions from text, stores the URLs and titles in
  157. // hash references.
  158. //
  159.  
  160.     // Link defs are in the form: ^[id]: url "optional title"
  161.  
  162.     /*
  163.         var text = text.replace(/
  164.                 ^[ ]{0,3}\[(.+)\]:  // id = $1  attacklab: g_tab_width - 1
  165.                   [ \t]*
  166.                   \n?                // maybe *one* newline
  167.                   [ \t]*
  168.                 <?(\S+?)>?            // url = $2
  169.                   [ \t]*
  170.                   \n?                // maybe one newline
  171.                   [ \t]*
  172.                 (?:
  173.                   (\n*)                // any lines skipped = $3 attacklab: lookbehind removed
  174.                   ["(]
  175.                   (.+?)                // title = $4
  176.                   [")]
  177.                   [ \t]*
  178.                 )?                    // title is optional
  179.                 (?:\n+|$)
  180.               /gm,
  181.               function(){...});
  182.     */
  183.     var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,
  184.         function (wholeMatch,m1,m2,m3,m4) {
  185.             m1 = m1.toLowerCase();
  186.             g_urls[m1] = _EncodeAmpsAndAngles(m2);  // Link IDs are case-insensitive
  187.             if (m3) {
  188.                 // Oops, found blank lines, so it's not a title.
  189.                 // Put back the parenthetical statement we stole.
  190.                 return m3+m4;
  191.             } else if (m4) {
  192.                 g_titles[m1] = m4.replace(/"/g,""");
  193.             }
  194.  
  195.             // Completely remove the definition from the text
  196.             return "";
  197.         }
  198.     );
  199.  
  200.     return text;
  201. }
  202.  
  203.  
  204. var _HashHTMLBlocks = function(text) {
  205.     // attacklab: Double up blank lines to reduce lookaround
  206.     text = text.replace(/\n/g,"\n\n");
  207.  
  208.     // Hashify HTML blocks:
  209.     // We only want to do this for block-level HTML tags, such as headers,
  210.     // lists, and tables. That's because we still want to wrap <p>s around
  211.     // "paragraphs" that are wrapped in non-block-level tags, such as anchors,
  212.     // phrase emphasis, and spans. The list of tags we're looking for is
  213.     // hard-coded:
  214.     var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
  215.     var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
  216.  
  217.     // First, look for nested blocks, e.g.:
  218.     //   <div>
  219.     //     <div>
  220.     //     tags for inner block must be indented.
  221.     //     </div>
  222.     //   </div>
  223.     //
  224.     // The outermost tags must start at the left margin for this to match, and
  225.     // the inner nested divs must be indented.
  226.     // We need to do this before the next, more liberal match, because the next
  227.     // match will start at the first `<div>` and stop at the first `</div>`.
  228.  
  229.     // attacklab: This regex can be expensive when it fails.
  230.     /*
  231.         var text = text.replace(/
  232.         (                        // save in $1
  233.             ^                    // start of line  (with /m)
  234.             <($block_tags_a)    // start tag = $2
  235.             \b                    // word break
  236.                                 // attacklab: hack around khtml/pcre bug...
  237.             [^\r]*?\n            // any number of lines, minimally matching
  238.             </\2>                // the matching end tag
  239.             [ \t]*                // trailing spaces/tabs
  240.             (?=\n+)                // followed by a newline
  241.         )                        // attacklab: there are sentinel newlines at end of document
  242.         /gm,function(){...}};
  243.     */
  244.     text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement);
  245.  
  246.     //
  247.     // Now match more liberally, simply from `\n<tag>` to `</tag>\n`
  248.     //
  249.  
  250.     /*
  251.         var text = text.replace(/
  252.         (                        // save in $1
  253.             ^                    // start of line  (with /m)
  254.             <($block_tags_b)    // start tag = $2
  255.             \b                    // word break
  256.                                 // attacklab: hack around khtml/pcre bug...
  257.             [^\r]*?                // any number of lines, minimally matching
  258.             .*</\2>                // the matching end tag
  259.             [ \t]*                // trailing spaces/tabs
  260.             (?=\n+)                // followed by a newline
  261.         )                        // attacklab: there are sentinel newlines at end of document
  262.         /gm,function(){...}};
  263.     */
  264.     text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement);
  265.  
  266.     // Special case just for <hr />. It was easier to make a special case than
  267.     // to make the other regex more complicated.
  268.  
  269.     /*
  270.         text = text.replace(/
  271.         (                        // save in $1
  272.             \n\n                // Starting after a blank line
  273.             [ ]{0,3}
  274.             (<(hr)                // start tag = $2
  275.             \b                    // word break
  276.             ([^<>])*?            //
  277.             \/?>)                // the matching end tag
  278.             [ \t]*
  279.             (?=\n{2,})            // followed by a blank line
  280.         )
  281.         /g,hashElement);
  282.     */
  283.     text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);
  284.  
  285.     // Special case for standalone HTML comments:
  286.  
  287.     /*
  288.         text = text.replace(/
  289.         (                        // save in $1
  290.             \n\n                // Starting after a blank line
  291.             [ ]{0,3}            // attacklab: g_tab_width - 1
  292.             <!
  293.             (--[^\r]*?--\s*)+
  294.             >
  295.             [ \t]*
  296.             (?=\n{2,})            // followed by a blank line
  297.         )
  298.         /g,hashElement);
  299.     */
  300.     text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,hashElement);
  301.  
  302.     // PHP and ASP-style processor instructions (<?...?> and <%...%>)
  303.  
  304.     /*
  305.         text = text.replace(/
  306.         (?:
  307.             \n\n                // Starting after a blank line
  308.         )
  309.         (                        // save in $1
  310.             [ ]{0,3}            // attacklab: g_tab_width - 1
  311.             (?:
  312.                 <([?%])            // $2
  313.                 [^\r]*?
  314.                 \2>
  315.             )
  316.             [ \t]*
  317.             (?=\n{2,})            // followed by a blank line
  318.         )
  319.         /g,hashElement);
  320.     */
  321.     text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement);
  322.  
  323.     // attacklab: Undo double lines (see comment at top of this function)
  324.     text = text.replace(/\n\n/g,"\n");
  325.     return text;
  326. }
  327.  
  328. var hashElement = function(wholeMatch,m1) {
  329.     var blockText = m1;
  330.  
  331.     // Undo double lines
  332.     blockText = blockText.replace(/\n\n/g,"\n");
  333.     blockText = blockText.replace(/^\n/,"");
  334.  
  335.     // strip trailing blank lines
  336.     blockText = blockText.replace(/\n+$/g,"");
  337.  
  338.     // Replace the element text with a marker ("~KxK" where x is its key)
  339.     blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n";
  340.  
  341.     return blockText;
  342. };
  343.  
  344. var _RunBlockGamut = function(text) {
  345. //
  346. // These are all the transformations that form block-level
  347. // tags like paragraphs, headers, and list items.
  348. //
  349.     text = _DoHeaders(text);
  350.  
  351.     // Do Horizontal Rules:
  352.     var key = hashBlock("<hr />");
  353.     text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);
  354.     text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);
  355.     text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);
  356.  
  357.     text = _DoLists(text);
  358.     text = _DoCodeBlocks(text);
  359.     text = _DoBlockQuotes(text);
  360.  
  361.     // We already ran _HashHTMLBlocks() before, in Markdown(), but that
  362.     // was to escape raw HTML in the original Markdown source. This time,
  363.     // we're escaping the markup we've just created, so that we don't wrap
  364.     // <p> tags around block-level tags.
  365.     text = _HashHTMLBlocks(text);
  366.     text = _FormParagraphs(text);
  367.  
  368.     return text;
  369. }
  370.  
  371.  
  372. var _RunSpanGamut = function(text) {
  373. //
  374. // These are all the transformations that occur *within* block-level
  375. // tags like paragraphs, headers, and list items.
  376. //
  377.  
  378.     text = _DoCodeSpans(text);
  379.     text = _EscapeSpecialCharsWithinTagAttributes(text);
  380.     text = _EncodeBackslashEscapes(text);
  381.  
  382.     // Process anchor and image tags. Images must come first,
  383.     // because ![foo][f] looks like an anchor.
  384.     text = _DoImages(text);
  385.     text = _DoAnchors(text);
  386.  
  387.     // Make links out of things like `<http://example.com/>`
  388.     // Must come after _DoAnchors(), because you can use < and >
  389.     // delimiters in inline links like [this](<url>).
  390.     text = _DoAutoLinks(text);
  391.     text = _EncodeAmpsAndAngles(text);
  392.     text = _DoItalicsAndBold(text);
  393.  
  394.     // Do hard breaks:
  395.     text = text.replace(/  +\n/g," <br />\n");
  396.  
  397.     return text;
  398. }
  399.  
  400. var _EscapeSpecialCharsWithinTagAttributes = function(text) {
  401. //
  402. // Within tags -- meaning between < and > -- encode [\ ` * _] so they
  403. // don't conflict with their use in Markdown for code, italics and strong.
  404. //
  405.  
  406.     // Build a regex to find HTML tags and comments.  See Friedl's
  407.     // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
  408.     var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
  409.  
  410.     text = text.replace(regex, function(wholeMatch) {
  411.         var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");
  412.         tag = escapeCharacters(tag,"\\`*_");
  413.         return tag;
  414.     });
  415.  
  416.     return text;
  417. }
  418.  
  419. var _DoAnchors = function(text) {
  420. //
  421. // Turn Markdown link shortcuts into XHTML <a> tags.
  422. //
  423.     //
  424.     // First, handle reference-style links: [link text] [id]
  425.     //
  426.  
  427.     /*
  428.         text = text.replace(/
  429.         (                            // wrap whole match in $1
  430.             \[
  431.             (
  432.                 (?:
  433.                     \[[^\]]*\]        // allow brackets nested one level
  434.                     |
  435.                     [^\[]            // or anything else
  436.                 )*
  437.             )
  438.             \]
  439.  
  440.             [ ]?                    // one optional space
  441.             (?:\n[ ]*)?                // one optional newline followed by spaces
  442.  
  443.             \[
  444.             (.*?)                    // id = $3
  445.             \]
  446.         )()()()()                    // pad remaining backreferences
  447.         /g,_DoAnchors_callback);
  448.     */
  449.     text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);
  450.  
  451.     //
  452.     // Next, inline-style links: [link text](url "optional title")
  453.     //
  454.  
  455.     /*
  456.         text = text.replace(/
  457.             (                        // wrap whole match in $1
  458.                 \[
  459.                 (
  460.                     (?:
  461.                         \[[^\]]*\]    // allow brackets nested one level
  462.                     |
  463.                     [^\[\]]            // or anything else
  464.                 )
  465.             )
  466.             \]
  467.             \(                        // literal paren
  468.             [ \t]*
  469.             ()                        // no id, so leave $3 empty
  470.             <?(.*?)>?                // href = $4
  471.             [ \t]*
  472.             (                        // $5
  473.                 (['"])                // quote char = $6
  474.                 (.*?)                // Title = $7
  475.                 \6                    // matching quote
  476.                 [ \t]*                // ignore any spaces/tabs between closing quote and )
  477.             )?                        // title is optional
  478.             \)
  479.         )
  480.         /g,writeAnchorTag);
  481.     */
  482.     text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);
  483.  
  484.     //
  485.     // Last, handle reference-style shortcuts: [link text]
  486.     // These must come last in case you've also got [link test][1]
  487.     // or [link test](/foo)
  488.     //
  489.  
  490.     /*
  491.         text = text.replace(/
  492.         (                             // wrap whole match in $1
  493.             \[
  494.             ([^\[\]]+)                // link text = $2; can't contain '[' or ']'
  495.             \]
  496.         )()()()()()                    // pad rest of backreferences
  497.         /g, writeAnchorTag);
  498.     */
  499.     text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
  500.  
  501.     return text;
  502. }
  503.  
  504. var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
  505.     if (m7 == undefined) m7 = "";
  506.     var whole_match = m1;
  507.     var link_text   = m2;
  508.     var link_id     = m3.toLowerCase();
  509.     var url        = m4;
  510.     var title    = m7;
  511.  
  512.     if (url == "") {
  513.         if (link_id == "") {
  514.             // lower-case and turn embedded newlines into spaces
  515.             link_id = link_text.toLowerCase().replace(/ ?\n/g," ");
  516.         }
  517.         url = "#"+link_id;
  518.  
  519.         if (g_urls[link_id] != undefined) {
  520.             url = g_urls[link_id];
  521.             if (g_titles[link_id] != undefined) {
  522.                 title = g_titles[link_id];
  523.             }
  524.         }
  525.         else {
  526.             if (whole_match.search(/\(\s*\)$/m)>-1) {
  527.                 // Special case for explicit empty url
  528.                 url = "";
  529.             } else {
  530.                 return whole_match;
  531.             }
  532.         }
  533.     }
  534.  
  535.     url = escapeCharacters(url,"*_");
  536.     var result = "<a href=\"" + url + "\"";
  537.  
  538.     if (title != "") {
  539.         title = title.replace(/"/g,""");
  540.         title = escapeCharacters(title,"*_");
  541.         result +=  " title=\"" + title + "\"";
  542.     }
  543.  
  544.     result += ">" + link_text + "</a>";
  545.  
  546.     return result;
  547. }
  548.  
  549.  
  550. var _DoImages = function(text) {
  551. //
  552. // Turn Markdown image shortcuts into <img> tags.
  553. //
  554.  
  555.     //
  556.     // First, handle reference-style labeled images: ![alt text][id]
  557.     //
  558.  
  559.     /*
  560.         text = text.replace(/
  561.         (                        // wrap whole match in $1
  562.             !\[
  563.             (.*?)                // alt text = $2
  564.             \]
  565.  
  566.             [ ]?                // one optional space
  567.             (?:\n[ ]*)?            // one optional newline followed by spaces
  568.  
  569.             \[
  570.             (.*?)                // id = $3
  571.             \]
  572.         )()()()()                // pad rest of backreferences
  573.         /g,writeImageTag);
  574.     */
  575.     text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);
  576.  
  577.     //
  578.     // Next, handle inline images:  ![alt text](url "optional title")
  579.     // Don't forget: encode * and _
  580.  
  581.     /*
  582.         text = text.replace(/
  583.         (                        // wrap whole match in $1
  584.             !\[
  585.             (.*?)                // alt text = $2
  586.             \]
  587.             \s?                    // One optional whitespace character
  588.             \(                    // literal paren
  589.             [ \t]*
  590.             ()                    // no id, so leave $3 empty
  591.             <?(\S+?)>?            // src url = $4
  592.             [ \t]*
  593.             (                    // $5
  594.                 (['"])            // quote char = $6
  595.                 (.*?)            // title = $7
  596.                 \6                // matching quote
  597.                 [ \t]*
  598.             )?                    // title is optional
  599.         \)
  600.         )
  601.         /g,writeImageTag);
  602.     */
  603.     text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);
  604.  
  605.     return text;
  606. }
  607.  
  608. var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
  609.     var whole_match = m1;
  610.     var alt_text   = m2;
  611.     var link_id     = m3.toLowerCase();
  612.     var url        = m4;
  613.     var title    = m7;
  614.  
  615.     if (!title) title = "";
  616.  
  617.     if (url == "") {
  618.         if (link_id == "") {
  619.             // lower-case and turn embedded newlines into spaces
  620.             link_id = alt_text.toLowerCase().replace(/ ?\n/g," ");
  621.         }
  622.         url = "#"+link_id;
  623.  
  624.         if (g_urls[link_id] != undefined) {
  625.             url = g_urls[link_id];
  626.             if (g_titles[link_id] != undefined) {
  627.                 title = g_titles[link_id];
  628.             }
  629.         }
  630.         else {
  631.             return whole_match;
  632.         }
  633.     }
  634.  
  635.     alt_text = alt_text.replace(/"/g,""");
  636.     url = escapeCharacters(url,"*_");
  637.     var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";
  638.  
  639.     // attacklab: Markdown.pl adds empty title attributes to images.
  640.     // Replicate this bug.
  641.  
  642.     //if (title != "") {
  643.         title = title.replace(/"/g,""");
  644.         title = escapeCharacters(title,"*_");
  645.         result +=  " title=\"" + title + "\"";
  646.     //}
  647.  
  648.     result += " />";
  649.  
  650.     return result;
  651. }
  652.  
  653.  
  654. // headers have been disabled
  655. var _DoHeaders = function(text) {
  656.  
  657.     // Setext-style headers:
  658.     //    Header 1
  659.     //    ========
  660.     //
  661.     //    Header 2
  662.     //    --------
  663.     //
  664.     // text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
  665.     //     function(wholeMatch,m1){return hashBlock("<h1>" + _RunSpanGamut(m1) + "</h1>");});
  666.     //
  667.     // text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
  668.     //     function(matchFound,m1){return hashBlock("<h2>" + _RunSpanGamut(m1) + "</h2>");});
  669.  
  670.     // atx-style headers:
  671.     //  # Header 1
  672.     //  ## Header 2
  673.     //  ## Header 2 with closing hashes ##
  674.     //  ...
  675.     //  ###### Header 6
  676.     //
  677.  
  678.     /*
  679.         text = text.replace(/
  680.             ^(\#{1,6})                // $1 = string of #'s
  681.             [ \t]*
  682.             (.+?)                    // $2 = Header text
  683.             [ \t]*
  684.             \#*                        // optional closing #'s (not counted)
  685.             \n+
  686.         /gm, function() {...});
  687.     */
  688.  
  689.     // text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
  690.     //     function(wholeMatch,m1,m2) {
  691.     //         var h_level = m1.length;
  692.     //         return hashBlock("<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">");
  693.     //     });
  694.  
  695.     return text;
  696. }
  697.  
  698. // This declaration keeps Dojo compressor from outputting garbage:
  699. var _ProcessListItems;
  700.  
  701. var _DoLists = function(text) {
  702. // //
  703. // // Form HTML ordered (numbered) and unordered (bulleted) lists.
  704. // //
  705. //
  706. //     // attacklab: add sentinel to hack around khtml/safari bug:
  707. //     // http://bugs.webkit.org/show_bug.cgi?id=11231
  708. //     text += "~0";
  709. //
  710. //     // Re-usable pattern to match any entirel ul or ol list:
  711. //
  712. //     /*
  713. //         var whole_list = /
  714. //         (                                    // $1 = whole list
  715. //             (                                // $2
  716. //                 [ ]{0,3}                    // attacklab: g_tab_width - 1
  717. //                 ([*+-]|\d+[.])                // $3 = first list item marker
  718. //                 [ \t]+
  719. //             )
  720. //             [^\r]+?
  721. //             (                                // $4
  722. //                 ~0                            // sentinel for workaround; should be $
  723. //             |
  724. //                 \n{2,}
  725. //                 (?=\S)
  726. //                 (?!                            // Negative lookahead for another list item marker
  727. //                     [ \t]*
  728. //                     (?:[*+-]|\d+[.])[ \t]+
  729. //                 )
  730. //             )
  731. //         )/g
  732. //     */
  733. //     var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
  734. //
  735. //     if (g_list_level) {
  736. //         text = text.replace(whole_list,function(wholeMatch,m1,m2) {
  737. //             var list = m1;
  738. //             var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol";
  739. //
  740. //             // Turn double returns into triple returns, so that we can make a
  741. //             // paragraph for the last item in a list, if necessary:
  742. //             list = list.replace(/\n{2,}/g,"\n\n\n");;
  743. //             var result = _ProcessListItems(list);
  744. //
  745. //             // Trim any trailing whitespace, to put the closing `</$list_type>`
  746. //             // up on the preceding line, to get it past the current stupid
  747. //             // HTML block parser. This is a hack to work around the terrible
  748. //             // hack that is the HTML block parser.
  749. //             result = result.replace(/\s+$/,"");
  750. //             result = "<"+list_type+">" + result + "</"+list_type+">\n";
  751. //             return result;
  752. //         });
  753. //     } else {
  754. //         whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
  755. //         text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) {
  756. //             var runup = m1;
  757. //             var list = m2;
  758. //
  759. //             var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol";
  760. //             // Turn double returns into triple returns, so that we can make a
  761. //             // paragraph for the last item in a list, if necessary:
  762. //             var list = list.replace(/\n{2,}/g,"\n\n\n");;
  763. //             var result = _ProcessListItems(list);
  764. //             result = runup + "<"+list_type+">\n" + result + "</"+list_type+">\n";
  765. //             return result;
  766. //         });
  767. //     }
  768. //
  769. //     // attacklab: strip sentinel
  770. //     text = text.replace(/~0/,"");
  771.  
  772.     return text;
  773. }
  774.  
  775. _ProcessListItems = function(list_str) {
  776. //
  777. //  Process the contents of a single ordered or unordered list, splitting it
  778. //  into individual list items.
  779. //
  780.     // The $g_list_level global keeps track of when we're inside a list.
  781.     // Each time we enter a list, we increment it; when we leave a list,
  782.     // we decrement. If it's zero, we're not in a list anymore.
  783.     //
  784.     // We do this because when we're not inside a list, we want to treat
  785.     // something like this:
  786.     //
  787.     //    I recommend upgrading to version
  788.     //    8. Oops, now this line is treated
  789.     //    as a sub-list.
  790.     //
  791.     // As a single paragraph, despite the fact that the second line starts
  792.     // with a digit-period-space sequence.
  793.     //
  794.     // Whereas when we're inside a list (or sub-list), that line will be
  795.     // treated as the start of a sub-list. What a kludge, huh? This is
  796.     // an aspect of Markdown's syntax that's hard to parse perfectly
  797.     // without resorting to mind-reading. Perhaps the solution is to
  798.     // change the syntax rules such that sub-lists must start with a
  799.     // starting cardinal number; e.g. "1." or "a.".
  800.  
  801.     g_list_level++;
  802.  
  803.     // trim trailing blank lines:
  804.     list_str = list_str.replace(/\n{2,}$/,"\n");
  805.  
  806.     // attacklab: add sentinel to emulate \z
  807.     list_str += "~0";
  808.  
  809.     /*
  810.         list_str = list_str.replace(/
  811.             (\n)?                            // leading line = $1
  812.             (^[ \t]*)                        // leading whitespace = $2
  813.             ([*+-]|\d+[.]) [ \t]+            // list marker = $3
  814.             ([^\r]+?                        // list item text   = $4
  815.             (\n{1,2}))
  816.             (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))
  817.         /gm, function(){...});
  818.     */
  819.     list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
  820.         function(wholeMatch,m1,m2,m3,m4){
  821.             var item = m4;
  822.             var leading_line = m1;
  823.             var leading_space = m2;
  824.  
  825.             if (leading_line || (item.search(/\n{2,}/)>-1)) {
  826.                 item = _RunBlockGamut(_Outdent(item));
  827.             }
  828.             else {
  829.                 // Recursion for sub-lists:
  830.                 item = _DoLists(_Outdent(item));
  831.                 item = item.replace(/\n$/,""); // chomp(item)
  832.                 item = _RunSpanGamut(item);
  833.             }
  834.  
  835.             return  "<li>" + item + "</li>\n";
  836.         }
  837.     );
  838.  
  839.     // attacklab: strip sentinel
  840.     list_str = list_str.replace(/~0/g,"");
  841.  
  842.     g_list_level--;
  843.     return list_str;
  844. }
  845.  
  846.  
  847. var _DoCodeBlocks = function(text) {
  848. //
  849. //  Process Markdown `<pre><code>` blocks.
  850. //
  851.  
  852.     /*
  853.         text = text.replace(text,
  854.             /(?:\n\n|^)
  855.             (                                // $1 = the code block -- one or more lines, starting with a space/tab
  856.                 (?:
  857.                     (?:[ ]{4}|\t)            // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
  858.                     .*\n+
  859.                 )+
  860.             )
  861.             (\n*[ ]{0,3}[^ \t\n]|(?=~0))    // attacklab: g_tab_width
  862.         /g,function(){...});
  863.     */
  864.  
  865.     // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
  866.     text += "~0";
  867.  
  868.     text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
  869.         function(wholeMatch,m1,m2) {
  870.             var codeblock = m1;
  871.             var nextChar = m2;
  872.  
  873.             codeblock = _EncodeCode( _Outdent(codeblock));
  874.             codeblock = _Detab(codeblock);
  875.             codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
  876.             codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
  877.  
  878.             codeblock = "<pre><code>" + codeblock + "\n</code></pre>";
  879.  
  880.             return hashBlock(codeblock) + nextChar;
  881.         }
  882.     );
  883.  
  884.     // attacklab: strip sentinel
  885.     text = text.replace(/~0/,"");
  886.  
  887.     return text;
  888. }
  889.  
  890. var hashBlock = function(text) {
  891.     text = text.replace(/(^\n+|\n+$)/g,"");
  892.     return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";
  893. }
  894.  
  895.  
  896. var _DoCodeSpans = function(text) {
  897. //
  898. //   *  Backtick quotes are used for <code></code> spans.
  899. //
  900. //   *  You can use multiple backticks as the delimiters if you want to
  901. //     include literal backticks in the code span. So, this input:
  902. //
  903. //         Just type ``foo `bar` baz`` at the prompt.
  904. //
  905. //       Will translate to:
  906. //
  907. //         <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
  908. //
  909. //    There's no arbitrary limit to the number of backticks you
  910. //    can use as delimters. If you need three consecutive backticks
  911. //    in your code, use four for delimiters, etc.
  912. //
  913. //  *  You can use spaces to get literal backticks at the edges:
  914. //
  915. //         ... type `` `bar` `` ...
  916. //
  917. //       Turns to:
  918. //
  919. //         ... type <code>`bar`</code> ...
  920. //
  921.  
  922.     /*
  923.         text = text.replace(/
  924.             (^|[^\\])                    // Character before opening ` can't be a backslash
  925.             (`+)                        // $2 = Opening run of `
  926.             (                            // $3 = The code block
  927.                 [^\r]*?
  928.                 [^`]                    // attacklab: work around lack of lookbehind
  929.             )
  930.             \2                            // Matching closer
  931.             (?!`)
  932.         /gm, function(){...});
  933.     */
  934.  
  935.     text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
  936.         function(wholeMatch,m1,m2,m3,m4) {
  937.             var c = m3;
  938.             c = c.replace(/^([ \t]*)/g,"");    // leading whitespace
  939.             c = c.replace(/[ \t]*$/g,"");    // trailing whitespace
  940.             c = _EncodeCode(c);
  941.             return m1+"<code>"+c+"</code>";
  942.         });
  943.  
  944.     return text;
  945. }
  946.  
  947.  
  948. var _EncodeCode = function(text) {
  949. //
  950. // Encode/escape certain characters inside Markdown code runs.
  951. // The point is that in code, these characters are literals,
  952. // and lose their special Markdown meanings.
  953. //
  954.     // Encode all ampersands; HTML entities are not
  955.     // entities within a Markdown code span.
  956.     text = text.replace(/&/g,"&");
  957.  
  958.     // Do the angle bracket song and dance:
  959.     text = text.replace(/</g,"<");
  960.     text = text.replace(/>/g,">");
  961.  
  962.     // Now, escape characters that are magic in Markdown:
  963.     text = escapeCharacters(text,"\*_{}[]\\",false);
  964.  
  965. // jj the line above breaks this:
  966. //---
  967.  
  968. //* Item
  969.  
  970. //   1. Subitem
  971.  
  972. //            special char: *
  973. //---
  974.  
  975.     return text;
  976. }
  977.  
  978.  
  979. var _DoItalicsAndBold = function(text) {
  980.  
  981.     // <strong> must go first:
  982.     text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,
  983.         "<strong>$2</strong>");
  984.  
  985.     text = text.replace(/(\*)(?=\S)([^\r]*?\S)\1/g,
  986.         "<em>$2</em>");
  987.  
  988.     return text;
  989. }
  990.  
  991.  
  992. var _DoBlockQuotes = function(text) {
  993.  
  994.     /*
  995.         text = text.replace(/
  996.         (                                // Wrap whole match in $1
  997.             (
  998.                 ^[ \t]*>[ \t]?            // '>' at the start of a line
  999.                 .+\n                    // rest of the first line
  1000.                 (.+\n)*                    // subsequent consecutive lines
  1001.                 \n*                        // blanks
  1002.             )+
  1003.         )
  1004.         /gm, function(){...});
  1005.     */
  1006.  
  1007.     text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
  1008.         function(wholeMatch,m1) {
  1009.             var bq = m1;
  1010.  
  1011.             // attacklab: hack around Konqueror 3.5.4 bug:
  1012.             // "----------bug".replace(/^-/g,"") == "bug"
  1013.  
  1014.             bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0");    // trim one level of quoting
  1015.  
  1016.             // attacklab: clean up hack
  1017.             bq = bq.replace(/~0/g,"");
  1018.  
  1019.             bq = bq.replace(/^[ \t]+$/gm,"");        // trim whitespace-only lines
  1020.             bq = _RunBlockGamut(bq);                // recurse
  1021.  
  1022.             bq = bq.replace(/(^|\n)/g,"$1  ");
  1023.             // These leading spaces screw with <pre> content, so we need to fix that:
  1024.             bq = bq.replace(
  1025.                     /(\s*<pre>[^\r]+?<\/pre>)/gm,
  1026.                 function(wholeMatch,m1) {
  1027.                     var pre = m1;
  1028.                     // attacklab: hack around Konqueror 3.5.4 bug:
  1029.                     pre = pre.replace(/^  /mg,"~0");
  1030.                     pre = pre.replace(/~0/g,"");
  1031.                     return pre;
  1032.                 });
  1033.  
  1034.             return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
  1035.         });
  1036.     return text;
  1037. }
  1038.  
  1039.  
  1040. var _FormParagraphs = function(text) {
  1041. //
  1042. //  Params:
  1043. //    $text - string to process with html <p> tags
  1044. //
  1045.  
  1046.     // Strip leading and trailing lines:
  1047.     text = text.replace(/^\n+/g,"");
  1048.     text = text.replace(/\n+$/g,"");
  1049.  
  1050.     var grafs = text.split(/\n{2,}/g);
  1051.     var grafsOut = new Array();
  1052.  
  1053.     //
  1054.     // Wrap <p> tags.
  1055.     //
  1056.     var end = grafs.length;
  1057.     for (var i=0; i<end; i++) {
  1058.         var str = grafs[i];
  1059.  
  1060.         // if this is an HTML marker, copy it
  1061.         if (str.search(/~K(\d+)K/g) >= 0) {
  1062.             grafsOut.push(str);
  1063.         }
  1064.         else if (str.search(/\S/) >= 0) {
  1065.             str = _RunSpanGamut(str);
  1066.             // str = str.replace(/^([ \t]*)/g,"<p>");
  1067.             // str += "</p>"
  1068.             grafsOut.push(str);
  1069.         }
  1070.  
  1071.     }
  1072.  
  1073.     //
  1074.     // Unhashify HTML blocks
  1075.     //
  1076.     end = grafsOut.length;
  1077.     for (var i=0; i<end; i++) {
  1078.         // if this is a marker for an html block...
  1079.         while (grafsOut[i].search(/~K(\d+)K/) >= 0) {
  1080.             var blockText = g_html_blocks[RegExp.$1];
  1081.             blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs
  1082.             grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText);
  1083.         }
  1084.     }
  1085.  
  1086.     return grafsOut.join("\n\n");
  1087. }
  1088.  
  1089.  
  1090. var _EncodeAmpsAndAngles = function(text) {
  1091. // Smart processing for ampersands and angle brackets that need to be encoded.
  1092.  
  1093.     // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
  1094.     //   http://bumppo.net/projects/amputator/
  1095.     text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&");
  1096.  
  1097.     // Encode naked <'s
  1098.     text = text.replace(/<(?![a-z\/?\$!])/gi,"<");
  1099.  
  1100.     return text;
  1101. }
  1102.  
  1103.  
  1104. var _EncodeBackslashEscapes = function(text) {
  1105. //
  1106. //   Parameter:  String.
  1107. //   Returns:    The string, with after processing the following backslash
  1108. //               escape sequences.
  1109. //
  1110.  
  1111.     // attacklab: The polite way to do this is with the new
  1112.     // escapeCharacters() function:
  1113.     //
  1114.     //     text = escapeCharacters(text,"\\",true);
  1115.     //     text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
  1116.     //
  1117.     // ...but we're sidestepping its use of the (slow) RegExp constructor
  1118.     // as an optimization for Firefox.  This function gets called a LOT.
  1119.  
  1120.     text = text.replace(/\\(\\)/g,escapeCharacters_callback);
  1121.     text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback);
  1122.     return text;
  1123. }
  1124.  
  1125.  
  1126. var _DoAutoLinks = function(text) {
  1127.  
  1128.     text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");
  1129.  
  1130.     // Email addresses: <address@domain.foo>
  1131.  
  1132.     /*
  1133.         text = text.replace(/
  1134.             <
  1135.             (?:mailto:)?
  1136.             (
  1137.                 [-.\w]+
  1138.                 \@
  1139.                 [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
  1140.             )
  1141.             >
  1142.         /gi, _DoAutoLinks_callback());
  1143.     */
  1144.     text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
  1145.         function(wholeMatch,m1) {
  1146.             return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
  1147.         }
  1148.     );
  1149.  
  1150.     return text;
  1151. }
  1152.  
  1153.  
  1154. var _EncodeEmailAddress = function(addr) {
  1155. //
  1156. //  Input: an email address, e.g. "foo@example.com"
  1157. //
  1158. //  Output: the email address as a mailto link, with each character
  1159. //    of the address encoded as either a decimal or hex entity, in
  1160. //    the hopes of foiling most address harvesting spam bots. E.g.:
  1161. //
  1162. //    <a href="mailto:foo@e
  1163. //       xample.com">foo
  1164. //       @example.com</a>
  1165. //
  1166. //  Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
  1167. //  mailing list: <http://tinyurl.com/yu7ue>
  1168. //
  1169.  
  1170.     // attacklab: why can't javascript speak hex?
  1171.     function char2hex(ch) {
  1172.         var hexDigits = '0123456789ABCDEF';
  1173.         var dec = ch.charCodeAt(0);
  1174.         return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15));
  1175.     }
  1176.  
  1177.     var encode = [
  1178.         function(ch){return "&#"+ch.charCodeAt(0)+";";},
  1179.         function(ch){return "&#x"+char2hex(ch)+";";},
  1180.         function(ch){return ch;}
  1181.     ];
  1182.  
  1183.     addr = "mailto:" + addr;
  1184.  
  1185.     addr = addr.replace(/./g, function(ch) {
  1186.         if (ch == "@") {
  1187.                // this *must* be encoded. I insist.
  1188.             ch = encode[Math.floor(Math.random()*2)](ch);
  1189.         } else if (ch !=":") {
  1190.             // leave ':' alone (to spot mailto: later)
  1191.             var r = Math.random();
  1192.             // roughly 10% raw, 45% hex, 45% dec
  1193.             ch =  (
  1194.                     r > .9  ?    encode[2](ch)   :
  1195.                     r > .45 ?    encode[1](ch)   :
  1196.                                 encode[0](ch)
  1197.                 );
  1198.         }
  1199.         return ch;
  1200.     });
  1201.  
  1202.     addr = "<a href=\"" + addr + "\">" + addr + "</a>";
  1203.     addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part
  1204.  
  1205.     return addr;
  1206. }
  1207.  
  1208.  
  1209. var _UnescapeSpecialChars = function(text) {
  1210. //
  1211. // Swap back in all the special characters we've hidden.
  1212. //
  1213.     text = text.replace(/~E(\d+)E/g,
  1214.         function(wholeMatch,m1) {
  1215.             var charCodeToReplace = parseInt(m1);
  1216.             return String.fromCharCode(charCodeToReplace);
  1217.         }
  1218.     );
  1219.     return text;
  1220. }
  1221.  
  1222.  
  1223. var _Outdent = function(text) {
  1224. //
  1225. // Remove one level of line-leading tabs or spaces
  1226. //
  1227.  
  1228.     // attacklab: hack around Konqueror 3.5.4 bug:
  1229.     // "----------bug".replace(/^-/g,"") == "bug"
  1230.  
  1231.     text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width
  1232.  
  1233.     // attacklab: clean up hack
  1234.     text = text.replace(/~0/g,"")
  1235.  
  1236.     return text;
  1237. }
  1238.  
  1239. var _Detab = function(text) {
  1240. // attacklab: Detab's completely rewritten for speed.
  1241. // In perl we could fix it by anchoring the regexp with \G.
  1242. // In javascript we're less fortunate.
  1243.  
  1244.     // expand first n-1 tabs
  1245.     text = text.replace(/\t(?=\t)/g,"    "); // attacklab: g_tab_width
  1246.  
  1247.     // replace the nth with two sentinels
  1248.     text = text.replace(/\t/g,"~A~B");
  1249.  
  1250.     // use the sentinel to anchor our regex so it doesn't explode
  1251.     text = text.replace(/~B(.+?)~A/g,
  1252.         function(wholeMatch,m1,m2) {
  1253.             var leadingText = m1;
  1254.             var numSpaces = 4 - leadingText.length % 4;  // attacklab: g_tab_width
  1255.  
  1256.             // there *must* be a better way to do this:
  1257.             for (var i=0; i<numSpaces; i++) leadingText+=" ";
  1258.  
  1259.             return leadingText;
  1260.         }
  1261.     );
  1262.  
  1263.     // clean up sentinels
  1264.     text = text.replace(/~A/g,"    ");  // attacklab: g_tab_width
  1265.     text = text.replace(/~B/g,"");
  1266.  
  1267.     return text;
  1268. }
  1269.  
  1270.  
  1271. //
  1272. //  attacklab: Utility functions
  1273. //
  1274.  
  1275.  
  1276. var escapeCharacters = function(text, charsToEscape, afterBackslash) {
  1277.     // First we have to escape the escape characters so that
  1278.     // we can build a character class out of them
  1279.     var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g,"\\$1") + "])";
  1280.  
  1281.     if (afterBackslash) {
  1282.         regexString = "\\\\" + regexString;
  1283.     }
  1284.  
  1285.     var regex = new RegExp(regexString,"g");
  1286.     text = text.replace(regex,escapeCharacters_callback);
  1287.  
  1288.     return text;
  1289. }
  1290.  
  1291.  
  1292. var escapeCharacters_callback = function(wholeMatch,m1) {
  1293.     var charCodeToEscape = m1.charCodeAt(0);
  1294.     return "~E"+charCodeToEscape+"E";
  1295. }
  1296.  
  1297. } // end of Showdown.converter
  1298.